"""NagMenu.py
Simple repetitive reminders.
"""

_version = "0.7.0"

__history__ = """History:
0.5.0 4/9/2010  First "full" version of the code
0.6.0 4/16/2010 Option in nags to stay until clicked.
    All wxTimers are created as one shot timers, and recreated with the same ID if 
    the nag.repeat flag is True. 
0.7.0 4/25/2010 Added preferences window as the main frame for the program
"""

import wx
import wx.lib.intctrl
from wx.lib import stattext
from wx.lib.masked import TimeCtrl
import os
import ConfigParser
import os
import ConfigParser

try:
    from agw import toasterbox as TB
except ImportError: # if it's not there locally, try the wxPython lib.
    import wx.lib.agw.toasterbox as TB

_defaults = dict([('popupposition', '3'), 
    ('popuplinger', '2500'),
    ('fontcolor', '#000000'),
    ('fontdesc', '0;-11;0;0;0;400;0;0;0;0;0;0;0;0;MS Shell Dlg 2'),
    ] )
    
class LocalTB(TB.ToasterBox):
    def __init__(self, *args, **kwargs):
        TB.ToasterBox.__init__(self, *args, **kwargs)
        self._nagID = None
    
    def Notify(self):
        TB.ToasterBox.Notify(self)
        #~ print 'closing box', self._popuptext
        app = wx.GetApp()
        nag = app.Nags.get(self.GetNagID(), None)
        if nag is None:
            raise ValueError, "cannot find nag %d" % self.GetNagID()
        if nag.repeat:
            nag.Start()
    
    def SetNagID(self, num):
        self._nagID = num
    
    def GetNagID(self):
        return self._nagID
        
class NagError(Exception):
    pass

class Nag(object):
    """Nag(name, message, time=5000, repeat=False, click = False, id=None)
    name [required] -- string used in the menu
    message [required] -- string used in the nag window
    time [integer default 5000 ms] -- time until the nag (or between nags) in millisecs
    repeat [boolean default False] -- if the nag is repetitive
    click [boolean default False] -- stick around until noticed, or disappear
    id [wx.ID default None] -- used internally
    """
    def __init__(self, name, message="Nag", time = 5000, repeat = False, click=False, id=None):
        self.name = name
        self.message = message
        
        self.time = time
        self.repeat = repeat
        self.click = click
        self.timer = None
        if id is None:
            self.id = wx.NewId()

        if not self.name.strip():
            raise NagError, "Name cannot be blank string"
        if not self.message.strip():
            raise NagError, "Message cannot be blank string"
        try:
            self.time = int(time)
        except:
            raise NagError, "Time must be an integer"
        if not isinstance(self.time, int):
            raise NagError, "Time must be an integer"
        if not isinstance(self.repeat, bool):
            raise NagError, "Repeat must be a boolean value"
        if not isinstance(self.click, bool):
            raise NagError, "Click must be a boolean value"
        if not isinstance(self.id, int):
            raise NagError, "ID must be an integer"
        
        # get rid of extra white space (especially tab)
        self.message = ' '.join(self.message.split())
        
        wx.GetApp().Bind(wx.EVT_TIMER, wx.GetApp().OnTimerEvent, id=self.id)
        wx.GetApp().Bind(wx.EVT_MENU, wx.GetApp().OnTimerItem, id = self.id)
    
    def Start(self, time = None, repeat = None):
        if time is None: time = self.time
        if repeat is None: repeat = self.repeat
        if self.timer is None:
            self.timer = wx.Timer(wx.GetApp(), self.id)
        
        #~ the wxTimer is always called with isOneShot = True
        #~ When the ToasterBox window closes (through popuplinger or a click) the Start method is called if nag.repeat == True
        self.timer.Start(time, True)
        #~ self.timer.Start(time, not repeat)
        
    def Stop(self):
        if self.timer is None:
            return True
        self.timer.Stop()
        #~ print "timer stopped"

    def IsRunning(self):
        if self.timer is None:
            return False
        else:
            return self.timer.IsRunning() or (self.repeat and not self.timer.IsRunning() )
    
    def toString(self):
        """String representation of the nag"""
        return '\t'.join([self.name, self.message, str(self.time), str(self.repeat), str(self.click), str(self.IsRunning())]) + '\n'



class NagDialog(wx.Dialog):
    def __init__(self, parent, nag = None):
        title = 'New Nag' if nag is None else nag.name
        wx.Dialog.__init__(self, parent, title=title, style=wx.DEFAULT_DIALOG_STYLE)
        panel = wx.Panel(self)
        sizer = wx.BoxSizer(wx.VERTICAL)
        row = wx.BoxSizer(wx.HORIZONTAL)
        row.Add(wx.StaticText(panel, label="Name:") )
        row.Add(wx.TextCtrl(panel, name = 'name'), 1, wx.EXPAND | wx.GROW | wx.LEFT, 6)
        sizer.Add(row, 0, wx.EXPAND | wx.GROW | wx.ALL, 6)
        
        row = wx.BoxSizer(wx.HORIZONTAL)
        row.Add(wx.StaticText(panel, label="Message:") )
        row.Add(wx.TextCtrl(panel, name='message'), 1, wx.EXPAND | wx.GROW | wx.LEFT, 6)
        sizer.Add(row, 0, wx.EXPAND | wx.GROW | wx.ALL, 6)
        
        row = wx.BoxSizer(wx.HORIZONTAL)
        row.Add(wx.StaticText(panel, label='Time'))
        row.Add(TimeCtrl(panel, value='00:00:00', fmt24hr = True, name='time'), 0, wx.LEFT, 3)
        sizer.Add(row, 0, wx.EXPAND | wx.GROW | wx.ALL, 6)
        row = wx.BoxSizer(wx.HORIZONTAL)
        row.Add(wx.CheckBox(panel, label = 'Repeat', name='repeat'), 0, wx.LEFT, 6)
        row.Add(wx.CheckBox(panel, label = 'Dismiss on click', name='click'), 0, wx.LEFT, 6)
        row.Add(wx.CheckBox(panel, label = 'Start', name='start'), 0, wx.LEFT, 6)
        sizer.Add(row, 0, wx.EXPAND | wx.GROW | wx.ALL, 6)
        
        btns = self.CreateSeparatedButtonSizer(wx.OK | wx.CANCEL)
        
        s = wx.BoxSizer(wx.VERTICAL)
        s.Add(panel, 1, wx.EXPAND | wx.GROW )
        s.Add(btns, 0, wx.EXPAND | wx.ALIGN_CENTER_HORIZONTAL)
        panel.SetSizer(sizer)
        self.SetSizerAndFit(s)
        self.Layout()

_tileSize = wx.Size(30, 20)
_tilePoint = wx.Point(_tileSize.GetWidth(), _tileSize.GetHeight())
_text = """Nag controls
Select the position, linger, and font of your nags.
"""
class Tile(wx.Window):
    def __init__(self, parent, pos, name):
        wx.Window.__init__(self, parent=parent, name = name, size=_tileSize, pos = pos, style=wx.BORDER_SUNKEN)
        self.SetBackgroundColour(wx.NamedColour('white') )



class NagControlFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, title = "Nag Preferences", 
            style=wx.CAPTION | wx.MINIMIZE_BOX | wx.SYSTEM_MENU | wx.FRAME_NO_TASKBAR)
        self.SetIcon(wx.Icon("nag.ico", wx.BITMAP_TYPE_ICO))
        self.Config = wx.GetApp().Config
        
        panel = wx.Panel(self)
        
        fivebox = self.fivebox= wx.Panel(panel, size=_tileSize.Scale(1, 1), style=wx.BORDER_SIMPLE)
        fivebox.SetBackgroundColour(wx.NamedColour('dark grey') )
        ul = Tile(fivebox,  pos = (0, 0), name="Upper Left")
        ur = Tile(fivebox, pos = (_tilePoint.x, 0), name="Upper Right")
        ll = Tile(fivebox, pos = (0, _tilePoint.y), name = "Lower Left")
        lr = Tile(fivebox, pos = _tilePoint, name="Lower Right")
        #~ c = Tile(fivebox, pos = (30, 20), name="center")
        
        self.Corner = wx.StaticText(panel, label="", style=wx.ALIGN_CENTRE | wx.ST_NO_AUTORESIZE)
        
        #~ self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnterWindow, fivebox)
        ul.Bind(wx.EVT_LEFT_UP, self.OnCornerClick)
        ll.Bind(wx.EVT_LEFT_UP, self.OnCornerClick)
        ur.Bind(wx.EVT_LEFT_UP, self.OnCornerClick)
        lr.Bind(wx.EVT_LEFT_UP, self.OnCornerClick)
        #~ c.Bind(wx.EVT_LEFT_UP, self.OnEnterWindow)
        self.subpanels = [ul, ll, ur, lr]
        
        posbox = wx.StaticBox(panel, label="Popup Position")
        psizer= wx.StaticBoxSizer(posbox, wx.VERTICAL)
        psizer.Add(fivebox, 0, wx.ALL, 10)
        psizer.Add(self.Corner, 0, wx.EXPAND | wx.BOTTOM, 10)
        
        lingerbox = wx.StaticBox(panel, label="Popup Linger")
        lsizer = wx.StaticBoxSizer(lingerbox, wx.VERTICAL)
        lingertext = wx.StaticText(panel, label="Enter the linger time in milliseconds")
        self.lingerctrl = wx.lib.intctrl.IntCtrl(panel, min = 500, value=500, limited=True, style=wx.TE_RIGHT)
        self.lingerctrl.SetValue(self.Config.getint('DEFAULT', 'popuplinger') )
        
        lsizer.Add(lingertext, 0, wx.ALL, 5)
        lsizer.Add(self.lingerctrl, 0, wx.ALL, 5)
        
        fontbutton = wx.Button(panel, label="Set Font")
        fontbutton.Bind(wx.EVT_BUTTON, self.OnFontSelection)
        self.fontcolour = wx.BLACK
        
        self.fontsample = wx.StaticText(panel, label = "Nag Text", 
            style= wx.ALIGN_CENTRE | wx.ST_NO_AUTORESIZE) 
        
        self.fontsample.SetFont( wx.FontFromNativeInfoString(self.Config.get('DEFAULT', 'fontdesc') ) )
        c = wx.Colour()
        c.SetFromString(self.Config.get('DEFAULT','fontcolor') )
        self.fontsample.SetForegroundColour( c ) 
        self.fontsample.SetBackgroundColour(wx.WHITE)

        sizer = wx.BoxSizer(wx.VERTICAL)
        text = wx.StaticText(panel, label=_text)
        
        sizer.Add(text, 0, wx.ALL, 5)
        row = wx.BoxSizer(wx.HORIZONTAL)
        row.Add(psizer, 0, wx.ALL, 5)
        row.Add(lsizer, 0, wx.ALL, 5)
        sizer.Add(row)
        
        row = wx.BoxSizer(wx.HORIZONTAL)
        row.Add(fontbutton, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
        row.Add(self.fontsample, 1, wx.ALIGN_CENTER_VERTICAL | wx.EXPAND | wx.GROW, 5)
        sizer.Add(row, 0, wx.EXPAND | wx.GROW)
    
        
        panel.SetSizer(sizer)
        
        self.Layout()
        self.CentreOnScreen()
        
        self.Bind(wx.EVT_ICONIZE, self.OnMinimize)
    
    def OnMinimize(self, event):
        self.Hide()
        
        
    def OnFontSelection(self, event):
        data = wx.FontData()
        data.EnableEffects(True)
        c = wx.Colour()
        c.SetFromString( self.Config.get('DEFAULT', 'fontcolor') )
        data.SetColour(c)
        data.SetInitialFont(wx.FontFromNativeInfoString(self.Config.get('DEFAULT', 'fontdesc') ) )
        
        dlg = wx.FontDialog(self, data)
        
        if dlg.ShowModal() == wx.ID_OK:
            data = dlg.GetFontData()
            font = data.GetChosenFont()
            colour = data.GetColour()
            
            self.fontsample.SetFont(font)
            self.fontsample.SetForegroundColour(colour)
            
            self.fontsample.Refresh()
            
            self.Config.set('DEFAULT', 'fontcolor', colour.GetAsString(wx.C2S_HTML_SYNTAX) )
            self.Config.set('DEFAULT', 'fontdesc', font.GetNativeFontInfoDesc())
            
    
    def ResetAll(self):
        for p in self.subpanels:
            p.SetBackgroundColour(wx.NamedColour('white') )
    
    def OnCornerClick(self, event):
        print "click", event.GetEventObject().Name
        self.ResetAll()
        event.GetEventObject().SetBackgroundColour(wx.NamedColour('blue') )
        self.fivebox.Refresh()
        self.Corner.SetLabel(event.GetEventObject().Name)
        self.Corner.Refresh()
        
        


class App(wx.App):
    def OnInit(self):
        self.SetAppName('Nag')
        self.Config = ConfigParser.SafeConfigParser(_defaults)
        self.Config.read(['nag.cfg'])
        
        self.Nags = {}
        dir = os.getcwd()
        self.nagfilepath = os.path.join(dir, 'nags.txt')
        
        try:
            with open(self.nagfilepath, 'r') as f:
                for line in f.readlines():
                    print line
                    if line:
                        name, message, time, repeat, click, run = line.strip().split('\t')
                        nag = self.AddNag(Nag(name, message, int(time), repeat=='True', click=='True') )
                        if nag and run == 'True':
                            nag.Start()
        except Exception as e:
            print e
            

        ### Standard IDs to use in the menu
        self.DeleteNagID = wx.NewId()
        
        self.NagUpperLeft = wx.NewId()
        self.NagUpperRight = wx.NewId()
        self.NagBottomLeft = wx.NewId()
        self.NagBottomRight = wx.NewId()
        self._position= dict([(self.NagUpperLeft, 0), (self.NagUpperRight, 1),
            (self.NagBottomLeft, 2), (self.NagBottomRight, 3)] )
        self._lookup = dict( [(val, key) for key, val in self._position.items() ] )
        
        self.PreferencesID = wx.NewId()
        
        ### Task bar icon
        self.tbicon = wx.TaskBarIcon()
        icon = wx.Icon("nag.ico", wx.BITMAP_TYPE_ICO)
        self.tbicon.SetIcon(icon, 'Timer Manager')
        self.tbicon.Bind(wx.EVT_TASKBAR_LEFT_UP, self.OnTBClick)
        
        self.Bind(wx.EVT_MENU, self.OnNewNag, id = wx.ID_NEW)
        self.Bind(wx.EVT_MENU, self.OnDeleteNag, id = self.DeleteNagID)
        self.Bind(wx.EVT_MENU, self.OnAbout, id = wx.ID_ABOUT)
        self.Bind(wx.EVT_MENU, self.SetNagPosition, id = self.NagUpperLeft, id2 = self.NagBottomRight)
        self.Bind(wx.EVT_MENU, self.MenuClose, id=wx.ID_EXIT)
        self.Bind(wx.EVT_MENU, self.OnPreferences, id= self.PreferencesID)
       
        ### Create a simple unshown frame to be a parent
        ### of the toasterboxes
        self.frame = NagControlFrame(None)
        self.SetTopWindow(self.frame)
        self.frame.Show(False)
        
        return True

        
    def SetNagPosition(self, event):
        try:
            self.Config.set('DEFAULT', 'popupposition', str(self._position[event.GetId()]))
        except:
            wx.MessageBox('Cannot set position', 'Oops', wx.ICON_ERROR)
            
    
    def OnPreferences(self, event):
        self.frame.Show(True)
        self.frame.Iconize(False)
        
    def OnNewNag(self, event):
        dlg = NagDialog(self.frame)
        if dlg.ShowModal() == wx.ID_OK:
            name = dlg.FindWindowByName('name').GetValue().strip()
            message = dlg.FindWindowByName('message').GetValue().strip()
            time = dlg.FindWindowByName('time').GetValue(as_wxTimeSpan=True).GetMilliseconds()
            repeat = dlg.FindWindowByName('repeat').GetValue()
            click = dlg.FindWindowByName('click').GetValue()
            start = dlg.FindWindowByName('start').GetValue()
            nag = Nag(name, message, time, repeat)
            try:
                nag = Nag(name, message, time, repeat, click)
            except Exception, e:
                wx.MessageBox(e, 'Nag Error', wx.OK | wx.ICON_ERROR)
                return False
            if start:
                nag.Start()
            self.AddNag(nag)

            

    def AddNag(self, nag):
        if not isinstance(nag, Nag):
            wx.Messagebox('Cannot Add a non-Nag thing')
            return False
        if nag.id in self.Nags:
            wx.MessageBox('Nag already exists')
            return False
        else:
            self.Nags[nag.id] = nag
            return nag
    
    def OnDeleteNag(self, event):
        dct = dict([(b.name, ID) for ID, b in sorted(self.Nags.items())])
        
        lst = sorted(dct.keys())
        dlg = wx.MultiChoiceDialog(self.frame, 
            "Select the nags to delete.\nThis is not undoable.",
            "Delete",
            lst)
        
        if dlg.ShowModal() == wx.ID_OK:
            selections = dlg.GetSelections()

            for sel in selections:
                self.Nags[dct[lst[sel]]].Stop()
                del self.Nags[ dct[lst[sel]] ]
            
    def OnTBClick(self, event):
        menu = wx.Menu()
        menu.Append(wx.ID_NEW, 'New Nag')
        posmenu = wx.Menu()
        posmenu.AppendCheckItem(self.NagUpperLeft, 'Upper Left')
        posmenu.AppendCheckItem(self.NagUpperRight, 'Upper Right')
        posmenu.AppendCheckItem(self.NagBottomLeft, 'Bottom Left')
        posmenu.AppendCheckItem(self.NagBottomRight, 'Bottom Right')
        menu.AppendSubMenu(posmenu, "Position")
        pos = self.Config.getint('DEFAULT', 'popupposition')
        
        posmenu.Check(self._lookup[pos], True)
        
        menu.AppendSeparator()
        for nagidx in sorted(self.Nags):
            nag = self.Nags[nagidx]
            mi = menu.AppendCheckItem(nag.id, nag.name)
            mi.Check(nag.IsRunning() )
        menu.AppendSeparator()
        menu.Append(self.DeleteNagID, 'Delete ...')
        menu.Append(self.PreferencesID, 'Preferences ...')
        menu.Append(wx.ID_ABOUT, 'About ...')
        menu.Append(wx.ID_EXIT, 'Exit')

        self.tbicon.PopupMenu(menu)

    def MenuClose(self, event):
        # Save nags to file
        f = open(os.path.join(os.getcwd(), 'nags.txt'), 'w')
        for idx in sorted(self.Nags):
            f.write(self.Nags[idx].toString())
        f.close()
        
        for nagidx in self.Nags:
            self.Nags[nagidx].Stop()
        
        self.tbicon.Destroy()
        self.Config.write(open('nag.cfg', 'w'))
        self.Exit()


    
    def OnTimerEvent(self, event):
        """OnTimerEvent(event)
        Creates a toaster box
        """
        nag = self.Nags.get(event.GetId(), None)
        if nag is None:
            return False
        
        closestyle = TB.TB_ONCLICK if nag.click else TB.TB_ONTIME
        tb = LocalTB(self.frame, closingstyle = closestyle, scrollType = TB.TB_SCR_TYPE_DU)
        tb.SetNagID( event.GetId() )

        tb.SetPopupText(nag.message)
        if closestyle == TB.TB_ONCLICK:
            tb.SetPopupPauseTime(360000)
        else:
            tb.SetPopupPauseTime(self.Config.getint('DEFAULT', 'popuplinger'))
        tb.SetPopupSize(wx.Size(300, 100) )
        tb.SetPopupPositionByInt(self.Config.getint('DEFAULT','popupposition'))
        c = wx.Colour()
        c.SetFromString( self.Config.get('DEFAULT', 'fontcolor') )
        tb.SetPopupTextColor(c)
        tb.SetPopupTextFont(wx.FontFromNativeInfoString(self.Config.get('DEFAULT', 'fontdesc') ) )
        tb.Play()
        
    def OnTimerItem(self, event):
        nag = self.Nags.get(event.GetId(), None)
        if nag is None:
            wx.MessageBox('Cannot find nag', 'Oops', wx.ICON_ERROR)
            return False
        if nag.IsRunning():
            nag.Stop()
        else:
            nag.Start()
    
    # Dead method
    def xxx_OnPopupClose(self, event):
        print "popupclose"
        #~ wx.MessageBox('done' ,'fin')
        event.Skip()
        
    def OnAbout(self, event):
        wx.MessageBox('Written by Josh English\nToaster Box by Andrea Gavanna',
            'About Nag %s' % _version)
        
        


app = App(0)
app.MainLoop()
app.Destroy()